﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Description;
using ProLinq.Toolkit;
using ProLinq.Wcf.Behaviors;
using ProLinq.Wcf.Configuration;

namespace ProLinq.Wcf.Description
{
	class QueryableDescriptionMutator : ServiceDescriptionMutator
	{
		private Settings _settings;

		public QueryableDescriptionMutator(Settings settings)
		{
			this._settings = settings;
		}

		public override void AlterDescription(ServiceEndpoint endpoint)
		{
			if (endpoint.Behaviors.Find<QueryableBehavior>() == null)
			{
				endpoint.Behaviors.Add(new QueryableBehavior(this._settings));
			}

			base.AlterDescription(endpoint);
		}

		public override void AlterDescription(OperationDescription operationDescription)
		{
			if (operationDescription.SyncMethod != null && operationDescription.SyncMethod.ReturnType.Is<IQueryable>())
			{
				this.ChangeOperationReturnType(operationDescription);
				this.AddOperationKnownTypes(operationDescription);
				this.SetupOperationBehaviors(operationDescription);
			}

			base.AlterDescription(operationDescription);
		}

		private void ChangeOperationReturnType(OperationDescription operationDescription)
		{
			MessagePartDescription returnValue =
				operationDescription.Messages.Where(m => m.Direction == MessageDirection.Output)
									.Select(m => m.Body.ReturnValue)
									.First();

			if (returnValue.Type.Is<QueryResult>())
				return;

			if (returnValue.Type.IsGenericType)
			{
				Type[] genericArgs = returnValue.Type.GenericTypeArguments;
				returnValue.Type = typeof(QueryResult<>).MakeGenericType(genericArgs);
			}
			else
			{
				returnValue.Type = typeof(QueryResult);
			}
		}

		private void AddOperationKnownTypes(OperationDescription operationDescription)
		{
			var elementType = operationDescription.SyncMethod.ReturnType.GenericTypeArguments[0];
			var types = new List<Type> {elementType, elementType.MakeArrayType()};

			foreach (var type in types)
			{
				if (!operationDescription.KnownTypes.Contains(type))
				{
					operationDescription.KnownTypes.Add(type);
				}
			}
		}

		private void SetupOperationBehaviors(OperationDescription operationDescription)
		{
			if (operationDescription.Behaviors.Find<QueryableDataContractSerializerOperationBehavior>() == null)
			{
				operationDescription.Behaviors.Remove<DataContractSerializerOperationBehavior>();
				operationDescription.Behaviors
					.Add(new QueryableDataContractSerializerOperationBehavior(operationDescription));
			}

			if (operationDescription.Behaviors.Find<QueryableOperationBehavior>() == null)
			{
				operationDescription.Behaviors.Add(new QueryableOperationBehavior(this._settings));
			}
		}
	}
}
